package ru.compscicenter.uno.net;

import ru.compscicenter.uno.controller.AController;
import ru.compscicenter.uno.log.Logger;
import ru.compscicenter.uno.model.Card;
import ru.compscicenter.uno.model.ModelException;
import ru.compscicenter.uno.model.Rules;
import ru.compscicenter.uno.model.StringRepresentation;
import ru.compscicenter.uno.log.Logger;
import ru.compscicenter.uno.wizard.Panel2Client;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;
import java.util.ArrayList;

public class TCPClient extends Thread {
    private Socket socket;
    private String ip;
    private int port;
    private AController controller;
    public boolean toRun = false;
    public boolean disconnected = false;
    private ArrayList<String> connectedNames;

    public TCPClient(AController controller, String ip, int port) throws IOException, IllegalArgumentException {
        connectedNames = new ArrayList<String>();
        this.controller = controller;
        this.ip = ip;
        this.port = port;
        SocketAddress saddr = new InetSocketAddress(ip, port);
        socket = new Socket();
        socket.connect(saddr, 2000);
    }

    public String getIp() {
        return ip;
    }

    public int getPort() {
        return port;
    }

    public void connect(String name) throws IOException {
        socket.setSoTimeout(2000);
        OutputStream os = socket.getOutputStream();
        os.write((AController.CONNECT + " " + name).getBytes());
        os.flush();
        socket.setSoTimeout(0);
    }

    public void disconnect() throws IOException {
        send(AController.DISCONNECT);
        disconnected = true;
    }
    
    public Rules receiveRules() throws IOException {
        OutputStream os = socket.getOutputStream();
        os.write((AController.RULES).getBytes());
        os.flush();
        return StringRepresentation.rulesFromStringRepresentation(receive());
    }

    public ArrayList<String> getConnectedNames() {
        return connectedNames;
    }

    public void send(String message) throws IOException {
        OutputStream os = socket.getOutputStream();
        String length = message.length() + "";
        while (length.length() < TCPServer.CHUNK_SIZE) {
            length = "0" + length;
        }
        os.write((length).getBytes());
        os.flush();
        Logger.write("Sent to server: " + message, Logger.Mode.ACTION);
        os.write(message.getBytes());
        os.flush();
    }

    public String receive() throws IOException {
        InputStream is = socket.getInputStream();
        byte[] length = new byte[100];
        int l = is.read(length, 0, TCPServer.CHUNK_SIZE);
        int toRead = Integer.parseInt(new String(length, 0, l));
//            System.out.println("TCPClient to read: " + toRead);
        byte[] buff = new byte[4096];
        int hasRead = 0;
        while (hasRead != toRead) {
            int size = is.read(buff, hasRead, toRead - hasRead);
            hasRead += size;
            //System.out.println("Now I read:" + size);
            //System.out.println("I have read totally:" + hasRead);
        }

        return new String(buff, 0, toRead);
    }

    public void run() {
        while (toRun) {
            String message = null;

            try {
                message = receive();
            } catch (Exception e) {
                if (!disconnected) {
                    Logger.write(e.toString(), Logger.Mode.ERROR);
                    Panel2Client.showMessage("Lost connection to one of the players. \nPlease, start this game again. Thank you!");
                    System.exit(0);
                } else {
                    break;
                }
            }

            boolean success = false;
        //    System.out.println("RECEIVED ::::: " + message);
            if (controller.getLastOperationSucceed() == -1 && message.startsWith(AController.SUCCESS)) {
               // System.out.println("555");
                Logger.write("Received last operation success" , Logger.Mode.ACTION);
                controller.setLastOperationSucceed(1);
                message = message.substring(message.indexOf(" ") + 1);
            } else if (controller.getLastOperationSucceed() == -1 && message.startsWith(AController.ERROR)) {
             //   System.out.println("666");
                Logger.write("Received last operation error" , Logger.Mode.ACTION);
                controller.setLastOperationSucceed(0);
                message = message.substring(message.indexOf(" ") + 1);
            }
            //System.out.println(controller.getLastOperationSucceed() + " 777");

            String[] dataParts = message.split(" ");

            try {
                if (message.startsWith(AController.NAMES)) {
                    String namesstr = message.split(":")[1];
                    Logger.write("Received names: " + namesstr, Logger.Mode.ACTION);
                    String[] names = namesstr.split(" ");
                    connectedNames.clear();
                    for (String name : names) {
                        Logger.write("Updated names of connected players", Logger.Mode.ACTION);
                        connectedNames.add(name);
                    }
                } else if (message.startsWith(AController.MODEL_DATA)) {
                    Logger.write("Received model data", Logger.Mode.ACTION);
                    controller.updateModelData(message.substring(message.indexOf(" ") + 1));
                    success = true;
                } else if (message.startsWith(AController.DO_DROP_CARD)) {
                    Logger.write("Received command to drop card: " + message, Logger.Mode.ACTION);
                    if (dataParts.length == 3) {
                        success = controller.getModel().doDropCard(Integer.parseInt(dataParts[1]), Integer.parseInt(dataParts[2]));
                    } else {
                        success = controller.getModel().doDropCard(Integer.parseInt(dataParts[1]), Integer.parseInt(dataParts[2]), Card.Color.valueOf(dataParts[3]));
                    }
                } else if (message.startsWith(AController.DO_TAKE_CARD)) {
                    Logger.write("Received command to take card: " + message, Logger.Mode.ACTION);
                    success = controller.getModel().doTakeCard(Integer.parseInt(dataParts[1]));
                } else if (message.startsWith(AController.DO_SAY_UNO)) {
                    Logger.write("Received command to say UNO: " + message, Logger.Mode.ACTION);
                    success = controller.getModel().doSayUno(Integer.parseInt(dataParts[1]));
                } else if (message.startsWith(AController.DO_SAY_NO_UNO)) {
                    Logger.write("Received command than not said UNO: " + message, Logger.Mode.ACTION);
                    success = controller.getModel().doSomebodyDidntSayUno(Integer.parseInt(dataParts[1]), Integer.parseInt(dataParts[2]));
                } else if (message.startsWith(AController.DO_END_TURN)) {
                    Logger.write("Received command to finish turn: " + message, Logger.Mode.ACTION);
                    success = controller.getModel().doEndTurn(Integer.parseInt(dataParts[1]));
                } else if (message.startsWith(AController.ERROR)) {
                    Logger.write("Received ERROR message " + message, Logger.Mode.ACTION);
                }
            } catch (ModelException e) {
                //never thrown by controller
                Logger.write("Model exception come in TCPClient o_O : " + e, Logger.Mode.ERROR);
            }
            controller.setIsChanged(success);
        }
    }
}
